#include <stdio.h>
#pragma GCC diagnostic ignored "-Wformat="

#include <stdint.h>   // for uintptr_t

#define GNOME_SORT_IMPLEMENTATION
#include "gnome_sort.h"

#include "acutest.h"

#ifndef UNUSED
#define UNUSED(X) ((void)(X))
#endif

#ifndef E
#define E(X) (sizeof(X)/sizeof(X[0]))
#endif

//******************************************************************************
// static helper functions
//******************************************************************************

//**************************************
// integer sorting helpers
//**************************************

static inline bool is_sorted_i(const gsort_t *data, size_t elements)
{
    for (size_t i = 0; i < elements - 1; i++)
    {
        if (data[i] > data[i + 1])
        {
            return false;
        }
    }
    return true;
}

//**************************************
// string sorting helpers
//**************************************

static bool str_gt(void *v1, void *v2)
{
    char **s1 = v1, **s2 = v2;
    return strcmp(*s1, *s2) > 0;
}

static void str_swap(void *v1, void *v2)
{
    char **s1 = v1, **s2 = v2;
    char *t = *s1;
    *s1 = *s2;
    *s2 = t;
}

static inline bool is_sorted_str(char **data, size_t elements)
{
    for (size_t i = 0; i < elements - 1; i++)
    {
        if (strcmp(data[i], data[i + 1]) > 1)
        {
            return false;
        }
    }
    return true;
}

static inline bool is_equal_str(const char **data1, char **data2, size_t elements)
{
    for (size_t i = 0; i < elements; i++)
    {
        if (strcmp(data1[i], data2[i]))
        {
            return false;
        }
    }
    return true;
}

static inline void print_array_str(char **arr, size_t elems)
{
    printf("\n");
    for (int i = 0; i < elems; i++)
    {
        printf("%d %0+16lx\n", i, arr[i]);
    }
}

static void gnome_sort_str(char *array[], size_t array_size)
{
    gnome_sort_p((void *)array, array_size, (gsort_gt_t)str_gt, (gsort_swap_t)str_swap);
}

//******************************************************************************
// test cases
//******************************************************************************

static void test_integer_sort(void)
{
    static const gsort_t test_data[] = { 1, 2, 1, 3, 4, 2, 6, 7, 9, 8, 5, 5, 8 };
    static const gsort_t sorted_data[] = { 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9 };
    static gsort_t working_data[E(test_data)];

    memcpy(working_data, test_data, sizeof(test_data));
    gnome_sort_i(working_data, E(working_data));
    TEST_CHECK(is_sorted_i(working_data, E(working_data)));
    TEST_CHECK(!memcmp(sorted_data, working_data, sizeof(sorted_data)));
    TEST_DUMP("test_data:   ", test_data, E(test_data) * sizeof(gsort_t));
    TEST_DUMP("working_data:", working_data, E(working_data) * sizeof(gsort_t));
    TEST_DUMP("sorted_data: ", sorted_data, E(sorted_data) * sizeof(gsort_t));
}

static void test_string_sort(void)
{
    static const char *test_data[] = { "one", "two", "three", "four", "five" };
    static const char *sorted_data[] = { "five", "four", "one", "three", "two" };
    static char *working_data[E(test_data)];

    memcpy(working_data, test_data, sizeof(working_data));
    gnome_sort_str(working_data, E(working_data));
    TEST_CHECK(is_sorted_str(working_data, E(working_data)));
    TEST_CHECK(is_equal_str(sorted_data, working_data, E(working_data)));
    TEST_DUMP("test_data:   ", test_data, E(test_data) * sizeof(gsort_t));
    TEST_DUMP("working_data:", working_data, E(working_data) * sizeof(gsort_t));
    TEST_DUMP("sorted_data: ", sorted_data, E(sorted_data) * sizeof(gsort_t));
}

TEST_LIST =
{
    { "integer sort", test_integer_sort },
    { "string sort",  test_string_sort  },
    { NULL, NULL }
};
